package fieldmaps.representation;

import java.awt.geom.Point2D;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.Map;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.CoordinateSequence;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.impl.CoordinateArraySequence;

import fieldmaps.files.BilFormatParser;

public class RasterFormatRepresentation {

	/* Raster implementation related values */
	private Object[][] rasterMapValues;
	private GeometryMapRepresentation rasterCells; 
	private int nf, nc;
	private Point2D.Double supLeftPoint;
	private Map<String, String> header = new HashMap<String, String>();

	public RasterFormatRepresentation() throws IllegalArgumentException {
		super();
		this.rasterCells = new GeometryMapRepresentation();
	}
	
	public void loadBilFormatFile (String fileName) throws IOException, FileNotFoundException {
		BilFormatParser bilFormat = new BilFormatParser();
		int[][] matrixData = bilFormat.getMatrixData(fileName);
		if (matrixData == null || matrixData[0] == null) {
			throw new FileNotFoundException();
		}
		this.nf = matrixData.length;
		this.nc = matrixData[0].length;
		this.rasterMapValues = new Object[this.nf][this.nc];
		for (int i = 0; i < this.nf; i++) {
			for (int j = 0; j < this.nc; j++) {
				this.rasterMapValues[i][j] = new Integer(matrixData[i][j]);
			}
		}
		this.header = bilFormat.getHeader();
		buildGeometry();
		System.out.println("---> File succesfully loaded!");
		System.out.println(this.rasterCells.cells.size());
	}
	
	private void buildGeometry() {
		int cellNumber = 0;
		this.supLeftPoint = getSupLeftPoint();
		for (int i = 0; i < this.nf; i++) {
			for (int j = 0; j < this.nc; j++) {
				CoordinateSequence coordenates = getCellCoordenates(cellNumber);
				LinearRing linearRing = new LinearRing(coordenates, new GeometryFactory());
				this.rasterCells.addCell(linearRing, (Integer)this.rasterMapValues[i][j]);
				cellNumber++;
			}
		}
	}

	private Point2D.Double getSupLeftPoint() {
		Point2D.Double p = new Point2D.Double();
		Double centerXFirstCell = Double.valueOf(this.header.get("ULXMAP"));
		Double centerYFirstCell = Double.valueOf(this.header.get("ULYMAP"));
		Double distanceXCells = Double.valueOf(this.header.get("XDIM"));
		Double distanceYCells = Double.valueOf(this.header.get("YDIM"));
		p.x = centerXFirstCell - (distanceXCells/2.0);
		p.y = centerYFirstCell - (distanceYCells/2.0);
		return p;
	}

	private CoordinateSequence getCellCoordenates(int cellNumber) {
		Coordinate p1 = new Coordinate();
		Coordinate p2 = new Coordinate();
		Coordinate p3 = new Coordinate();
		Coordinate p4 = new Coordinate();
		Double distanceXCells = Double.valueOf(this.header.get("XDIM"));
		Double distanceYCells = Double.valueOf(this.header.get("YDIM"));
		
		p1.x = this.supLeftPoint.x + (cellNumber % this.nc) * distanceXCells;
		p1.y = this.supLeftPoint.y + (cellNumber / this.nc) * distanceYCells;
		
		p2.x = p1.x + distanceXCells;
		p2.y = p1.y;
		
		p3.x = p1.x;
		p3.y = p1.y + distanceYCells;
		
		p4.x = p1.x + distanceXCells;
		p4.y = p1.y + distanceYCells;
		
		return new CoordinateArraySequence(new Coordinate[] {
				p1, p2, p3, p4, p1
		});
	}

	public Object getPointValueUsingConstantFunction (Point2D.Double point) {
		return getGeometryContainingPoint(point).getUserData();
	}

	public Polygon getGeometryContainingPoint (Point2D.Double point) {
		CoordinateSequence cSequence = new CoordinateArraySequence(new Coordinate[] {
				new Coordinate(point.x, point.y)
		}); 
		return this.rasterCells.getCell(new com.vividsolutions.jts.geom.Point(cSequence, new GeometryFactory()));
	}
	

	/**
	 * Arma un archivo ".bil" y un archivo ".hdr" con la informacion 
	 * que tiene esta instancia de raster.
	 * 
	 * Arranca con a grabacion que hace fede en el BilFormatParser
	 * 
	 * @param filename
	 */
	public void saveBilFormatFile(String filename) {
		if(filename != null && !filename.endsWith(".hdr")) {
			try {
				saveHeaderFile(filename + ".hdr");
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		if(filename != null && !filename.endsWith(".bil")) {
			try {
				saveBilFile(filename + ".bil");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void saveBilFile(String filename) throws IOException {
		FileOutputStream file = new FileOutputStream(filename);
		byte[] bytes = new byte[Integer.valueOf(this.header.get("NBITS")) / 8];
		int value;

		System.out.println ("Exporting data to bil file...");
		System.out.println ("--->Output file:" + filename);

		for (int i = 0; i < nf; i++) {
			for (int j = 0; j < nc; j++) {
				value = (Integer) (rasterMapValues[i][j]);
				for (int k = bytes.length-1; k >= 0; k--) {
					bytes[k] = (byte)(value & 0x000000FF);
					value -= bytes[k];
					value >>= k * 8;
				}
				file.write(bytes);
				file.flush();
			}
		}

		file.flush();
		file.close();

		System.out.println("--->The data was exported!");
	}
	
	private void saveHeaderFile(String filename) throws IOException {
		FileOutputStream result = new FileOutputStream(filename);
		OutputStreamWriter outputstream = new OutputStreamWriter(result);
		BufferedWriter writer = new BufferedWriter(outputstream);

		System.out.println ("Exporting data to header file...");
		System.out.println ("--->Output file:" + filename);

		//Ver todos estos campos
		writer.append("BYTEORDER\t" + this.header.get("BYTEORDER") + "\n");
		writer.append("LAYOUT\t" + this.header.get("LAYOUT") + "\n");
		writer.append("NROWS\t" + this.header.get("NROWS") + "\n");
		writer.append("NCOLS\t" + this.header.get("NCOLS") + "\n");
		writer.append("NBANDS\t" + this.header.get("NBANDS") + "\n");
		writer.append("NBITS\t" + this.header.get("NBITS") + "\n");
		writer.append("BANDROWBYTES\t" + this.header.get("BANDROWBYTES") + "\n");
		writer.append("TOTALROWBYTES\t" + this.header.get("TOTALROWBYTES") + "\n");
		writer.append("BANDGAPBYTES\t" + this.header.get("BANDGAPBYTES") + "\n");
		writer.append("NODATA\t" + this.header.get("NODATA") + "\n");
		writer.append("ULXMAP\t" + this.header.get("ULXMAP") + "\n");
		writer.append("ULYMAP\t" + this.header.get("ULYMAP") + "\n");
		writer.append("XDIM\t" + this.header.get("XDIM") + "\n");
		writer.append("YDIM\t" + this.header.get("YDIM") + "\n");
		writer.append("DatabaseName\t" + this.header.get("DatabaseName") + "\n");
		writer.append("Version\t" + this.header.get("Version") + "\n");
		writer.append("Release\t" + this.header.get("Release") + "\n");
		writer.append("Created\t" + this.header.get("Created") + "\n");
		writer.append("Projection\t" + this.header.get("Projection") + "\n");
		writer.append("Datum\t" + this.header.get("Datum") + "\n");
		writer.append("MinX\t" + this.header.get("MinX") + "\n");
		writer.append("MaxX\t" + this.header.get("MaxX") + "\n");
		writer.append("MinY\t" + this.header.get("MinY") + "\n");
		writer.append("MaxY\t" + this.header.get("MaxY") + "\n");
		writer.append("MinValue\t" + this.header.get("MinValue") + "\n");
		writer.append("MaxValue\t" + this.header.get("MaxValue") + "\n");
		writer.append("Variable\t" + this.header.get("Variable") + "\n");
		writer.append("month\t" + this.header.get("month") + "\n");
		writer.append("Units\t" + this.header.get("Units") + "\n");

		writer.flush();
		writer.close();
		outputstream.close();
		result.close();

		System.out.println("--->The data was exported!");
		System.out.println("");
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(this.header.toString());
		return sb.toString();
	}
	
}
